import React, { ComponentType, Consumer, createContext, ReactNode, useContext } from 'react';
import { ErrorBoundary } from './ErrorBoundary';
import { ErrorFallback, ErrorFallbackComponent, ErrorLike } from './ErrorFallback';
import { LoaderFallback, LoaderComponent } from './LoaderFallback';

/**
 * 应用程序接口类
 *
 * 该接口类声明了应用程序（管理）实例的接口定义，声明应用程序组件需要那些接口。
 *
 * 应用程序（管理）实例，可以是一个实现了 AppInterface 的类实例，也可以是一个 Object 结构。
 */
export interface AppInterface {

  /**
   * 应用程序启动接口，运行在 App 组件 componentDidMount
   */
  onLaunch?(): void | Promise<void>;

  /**
   * 当组件渲染出错时的处理接口
   *
   * @param error - 错误实例
   */
  onError?(error: ErrorLike): void;

  /**
   * 当错误发生时，App 渲染是否切换至 ErrorFallback
   *
   * @param error - 错误实例
   */
  shouldErrorFallback?(error: ErrorLike): boolean;

  /**
   * 错误异常回退组件声明
   *
   * 可以是一个 Element 实例（自动注入 Error 实例），也可以是一个组件
   */
  readonly ErrorFallback?: ErrorFallbackComponent;

  readonly Loader?: LoaderComponent;

  // 这里必须声明为 any ，unknown/never 满足不了设计需要，any 才可对实现的类进行自定义属性或方法声明
  /* eslint-disable  @typescript-eslint/no-explicit-any */
  [key: string]: any;
}

/**
 * 应用程序根节点组件属性
 *
 * 当应用程序实例和应用程序节点属性声明同时存在时，组件属性上的声明优先级永远高于应用程序实例
 *
 * 如：
 *
 * @example
 * 一个应用程序实例
 * ```
 * const YourApp: AppInterface = {
 *   onLaunch(): void {
 *     console.log('App Launch #2')
 *   }
 * }
 * ```
 *
 * @example
 * 应用程序组件
 * ```
 * const onLaunch = () => {
 *   console.log('App Launch #1')
 * };
 *
 * <AppRoot onLaunch={onLaunch} />
 * ```
 *
 * 如上述必然是：节点属性 > 实例类接口 执行。
 */
export type AppRootProps<App extends AppInterface> = {
  /**
   * 传入的应用程序实例
   */
  app?: App,
  /**
   * 应用启动接口
   */
  onLaunch?: () => void | Promise<void>,
  /**
   * 错误异常处理接口
   * @param error - 错误实例
   */
  onError?: (error?: ErrorLike) => void,
  /**
   * 错误异常回退组件声明
   * 可以是一个 Element 实例（自动注入 Error 实例），也可以是一个组件
   */
  errorFallback?: ErrorFallbackComponent,
  /**
   * 错误发生时，是否回退，默认值为 `true`
   */
  shouldErrorFallback?: boolean | ((error: ErrorLike) => boolean),

  loader?: LoaderComponent,

  children?: ReactNode | ReactNode[],
}

/**
 * 应用程序根节点组件状态
 */
export type AppRootState = {
  /**
   * 应用程序运行时错误
   */
  error?: ErrorLike,
  /**
   * 应用程序根节点是否初始化完毕
   *
   * 取决于 onLaunch （应用程序管理类实例 和 应用程序根节点组件）同步/异步 执行完毕
   */
  ready: boolean,
}

/**
 * 应用程序根节点抽象类
 */
export abstract class AppRoot<App extends AppInterface> extends ErrorBoundary<AppRootProps<App>, AppRootState> {

  state = {
    error: undefined,
    ready: false
  };

  constructor(props: AppRootProps<App>) {
    super(props);
  }

  componentDidMount(): void {
    if (!this.state.ready) {
      Promise.all([
        typeof this.props.onLaunch === 'function' && this.props.onLaunch(),
        typeof this.app?.onLaunch === 'function' && this.app?.onLaunch()
      ].filter(Boolean)).then(() => {
        this.setState({ ready: true });
      });
    }
  }

  /**
   * 获取当前应用程序管理实例
   *
   * 注意该结果可能为 undefined
   */
  get app(): App | undefined {
    return this.props.app;
  }

  onError(error: Error): void {
    super.onError(error);
    if (this.app != null) {
      typeof this.app.onError === 'function' && this.app.onError(error);
    }
  }

  shouldErrorFallback(error: ErrorLike): boolean {
    const { shouldErrorFallback } = this.props;
    if (shouldErrorFallback != null) {
      if (typeof shouldErrorFallback === 'function') {
        return shouldErrorFallback(error);
      }
      return shouldErrorFallback;
    }
    if (this.app != null && typeof this.app.shouldErrorFallback === 'function') {
      return this.app.shouldErrorFallback(error);
    }
    return true;
  }

  get fallback(): ErrorFallbackComponent {
    return this.props.errorFallback || this.app?.ErrorFallback;
  }

  get loader(): LoaderComponent {
    return this.props.loader || this.app?.Loader;
  }
}

export type AppContext<AppType extends AppInterface> = {
  app?: AppType,
  appError?: ErrorLike,
  appReady: boolean,
}

export type CreateAppProviderReturn<AppType extends AppInterface> = [
  ComponentType<AppRootProps<AppType>>,
  () => AppContext<AppType>,
  Consumer<AppContext<AppType>>,
];

/**
 * 创建一个应用程序根节点组件，并提供 useAppContext 的全局函数。
 *
 * 如果不通过该函数传入应用程序管理类实例，建议指定泛型
 *
 * @example
 * ```
 * export class YourAppClass implements AppInterface {}
 *
 * export const [
 *   YourApp,       // 你的应用专属的应用程序根节点组件
 *   useAppContext, // 你的应用专属的 useAppContext ，你可以基于此进一步封装出 useApp hook
 * ] = createAppProvider<YourAppClass>();
 * ```
 *
 * @example
 * 实际使用
 * ```
 * import React from 'react';
 * import { render } from 'react-dom';
 * import { YourApp, YourAppClass } from './App';
 *
 * // 延迟创建 应用程序实例，与 应用程序根节点同步创建，不需要额外写其他全局单例之类的写入
 * // 在整个应用程序环境，也不应该再引人 App ，而应该使用 useAppContext 获取
 * render((
 *   <React.StrictMode>
 *     <YourApp app={new YourAppClass}>
 *       <Routes>
 *         // .... 你的应用入口
 *       </Routes>
 *     </YourApp>
 *   </React.StrictMode>
 * ), document.getElementById('root'));
 * ```
 *
 * @param instance
 */
export const createAppProvider = <AppType extends AppInterface>(instance?: AppType): CreateAppProviderReturn<AppType> => {
  const AppContext = createContext<AppContext<AppType>>({ appReady: false });

  return [
    class App extends AppRoot<AppType> {
      get app(): AppType | undefined {
        return this.props.app || instance;
      }

      render() {
        return (
          <AppContext.Provider
            value={{ app: this.app, appError: this.state.error, appReady: this.state.ready }}
          >
            <ErrorFallback
              error={this.state.error}
              fallback={this.fallback}
              shouldFallback={this.shouldErrorFallback.bind(this)}
            >
              <LoaderFallback ready={this.state.ready} loader={this.loader}>
                {this.props.children}
              </LoaderFallback>
            </ErrorFallback>
          </AppContext.Provider>
        );
      }
    },
    () => useContext<AppContext<AppType>>(AppContext),
    AppContext.Consumer
  ];
};
